<i18n locale="en" lang="yaml">
generalCount        : '{n}'
taskCount           : '{n} Task | {n} Tasks'
</i18n>

<i18n locale="zh-CN" lang="yaml">
'Current browser-server time difference:': '当前浏览器与服务器时差：'

generalCount        : '{n} 个'
taskCount           : '{n} 个任务'
recentOperationCount: 最近 {n} 条

Expand All             : 展开所有服务
Collapse And Categorize: 折叠并归类

Overview    : 总览
Services    : 服务
Hostname    : 主机名
Process ID  : 进程 ID
Up Time     : 已运行
Queues      : 队列
Worker      : 工作单元
Process     : 工作进程
Delayed     : 延迟执行
Worker Queue: 工作队列
Queue Limit : 队列限制
Load        : 负载
Biz Metrics : 业务指标
Biz Entities: 业务实体

When the Work Queue length reaches the limit, Cron Jobs will stop generating new tasks: 当工作队列长度达到限制时，定时任务将停止产生新任务

Exclude Disabled Items: 不包括已禁用的项

Cron Job           : 定时任务
Triggers Per Second: 每秒触发次数
Triggers Per Minute: 每分钟触发次数
Triggers Per Hour  : 每小时触发次数
Triggers Per Day   : 每天触发次数
</i18n>

<!-- Generated by OpenCC START -->
<i18n locale="zh-HK" lang="yaml">
Biz Entities: 業務實體
Biz Metrics: 業務指標
Collapse And Categorize: 摺疊並歸類
Cron Job: 定時任務
'Current browser-server time difference:': 當前瀏覽器與服務器時差：
Delayed: 延遲執行
Exclude Disabled Items: 不包括已禁用的項
Expand All: 展開所有服務
Hostname: 主機名
Load: 負載
Overview: 總覽
Process: 工作進程
Process ID: 進程 ID
Queue Limit: 隊列限制
Queues: 隊列
Services: 服務
Triggers Per Day: 每天觸發次數
Triggers Per Hour: 每小時觸發次數
Triggers Per Minute: 每分鐘觸發次數
Triggers Per Second: 每秒觸發次數
Up Time: 已運行
When the Work Queue length reaches the limit, Cron Jobs will stop generating new tasks: 當工作隊列長度達到限制時，定時任務將停止產生新任務
Worker: 工作單元
Worker Queue: 工作隊列
generalCount: '{n} 個'
recentOperationCount: 最近 {n} 條
taskCount: '{n} 個任務'
</i18n>
<i18n locale="zh-TW" lang="yaml">
Biz Entities: 業務實體
Biz Metrics: 業務指標
Collapse And Categorize: 摺疊並歸類
Cron Job: 定時任務
'Current browser-server time difference:': 當前瀏覽器與伺服器時差：
Delayed: 延遲執行
Exclude Disabled Items: 不包括已禁用的項
Expand All: 展開所有服務
Hostname: 主機名
Load: 負載
Overview: 總覽
Process: 工作程序
Process ID: 程序 ID
Queue Limit: 佇列限制
Queues: 佇列
Services: 服務
Triggers Per Day: 每天觸發次數
Triggers Per Hour: 每小時觸發次數
Triggers Per Minute: 每分鐘觸發次數
Triggers Per Second: 每秒觸發次數
Up Time: 已執行
When the Work Queue length reaches the limit, Cron Jobs will stop generating new tasks: 當工作佇列長度達到限制時，定時任務將停止產生新任務
Worker: 工作單元
Worker Queue: 工作佇列
generalCount: '{n} 個'
recentOperationCount: 最近 {n} 條
taskCount: '{n} 個任務'
</i18n>
<!-- Generated by OpenCC END -->

<template>
  <transition name="fade">
    <el-container direction="vertical" v-show="$store.state.isLoaded">
      <!-- Header area -->
      <el-header height="60px">
        <h1>{{ $t('Overview') }}</h1>
      </el-header>

      <!-- List area -->
      <el-main>
        <span class="browser-server-time-diff" :class="Math.abs(browserServerTimeDiff) > DANGER_TIME_DIFF_MS ? 'text-bad' : 'text-info'">
          <i class="fa fa-fw" :class="Math.abs(browserServerTimeDiff) > DANGER_TIME_DIFF_MS ? 'fa-exclamation-triangle' : 'fa-exchange'"></i>
          {{ $t('Current browser-server time difference:') }}
          <TimeDuration :duration="browserServerTimeDiff" unit="ms" />
        </span>

        <el-divider content-position="left"><h1>{{ $t('Services') }} {{ $t('(') }}{{ $tc('generalCount', services.length) }}{{ $t(')') }}</h1></el-divider>
        <div class="service-group-expand-button">
          <el-link @click="serviceGroupCollapsed = !serviceGroupCollapsed">
            <i class="fa fa-angle-left" :class="{ 'fa-flip-horizontal': !serviceGroupCollapsed }"></i><i class="fa fa-angle-left" :class="{ 'fa-flip-horizontal': serviceGroupCollapsed }" style="margin-left: 2px;"></i>
            {{ serviceGroupCollapsed ? $t('Expand All') : $t('Collapse And Categorize') }}
          </el-link>
        </div>
        <template v-for="group in [ serviceGroup_servers, serviceGroup_workers, serviceGroup_beat]">
          <div class="service-group" :class="{ 'service-group-collapsed': serviceGroupCollapsed }">
            <el-card
              class="service-card"
              :class="{ 'service-card-collapsed': serviceGroupCollapsed, 'service-group-rest': serviceGroupCollapsed && i > 0 }"
              shadow="hover"
              v-for="service, i in group" :key="i">
              <div class="service-info" :class="{ 'service-info-collapsed': serviceGroupCollapsed }">
                <span class="service-name">{{ service.name }}</span>
                <table>
                  <template v-if="serviceGroupCollapsed && group.length > 1">
                    <tr>
                      <td>{{ $t('Version') }}</td>
                      <td>{{ $t(':') }}</td>
                      <td :class="{ 'text-bad': serviceGroupVersionEditions[service.name].length > 1 }">
                        <template v-for="versionEdition in serviceGroupVersionEditions[service.name]">
                          <code>{{ versionEdition }}</code><br>
                        </template>
                      </td>
                    </tr>

                    <tr>
                      <td>{{ $t('Time') }}</td>
                      <td>{{ $t(':') }}</td>
                      <td>
                        <code>AVG. {{ T.getDateTimeString(serviceGroupTime_avg[service.name] * 1000) }}</code>
                        <span :class="{ 'text-bad': Math.abs(serviceGroupTimeDiffMs_avg[service.name]) > DANGER_TIME_DIFF_MS }">
                          {{ $t('(') }}<TimeDuration :duration="serviceGroupTimeDiffMs_avg[service.name]" unit="ms" />{{ $t(')') }}
                        </span>
                      </td>
                    </tr>
                    <tr>
                      <td></td>
                      <td></td>
                      <td>
                        <code>MIN. {{ T.getDateTimeString(serviceGroupTime_min[service.name] * 1000) }}</code>
                        <span :class="{ 'text-bad': Math.abs(serviceGroupTimeDiffMs_min[service.name]) > DANGER_TIME_DIFF_MS }">
                          {{ $t('(') }}<TimeDuration :duration="serviceGroupTimeDiffMs_min[service.name]" unit="ms" />{{ $t(')') }}
                        </span>
                      </td>
                    </tr>
                    <tr>
                      <td></td>
                      <td></td>
                      <td>
                        <code>MAX. {{ T.getDateTimeString(serviceGroupTime_max[service.name] * 1000) }}</code>
                        <span :class="{ 'text-bad': Math.abs(serviceGroupTimeDiffMs_max[service.name]) > DANGER_TIME_DIFF_MS }">
                          {{ $t('(') }}<TimeDuration :duration="serviceGroupTimeDiffMs_max[service.name]" unit="ms" />{{ $t(')') }}
                        </span>
                      </td>
                    </tr>
                    <tr>
                      <td>{{ $t('Up Time') }}</td>
                      <td>{{ $t(':') }}</td>
                      <td><code>AVG. {{ T.duration(serviceGroupUptime_avg[service.name] * 1000) }}</code></td>
                    </tr>
                    <tr>
                      <td></td>
                      <td></td>
                      <td><code>MIN. {{ T.duration(serviceGroupUptime_min[service.name] * 1000) }}</code></td>
                    </tr>
                    <tr>
                      <td></td>
                      <td></td>
                      <td><code>MAX. {{ T.duration(serviceGroupUptime_max[service.name] * 1000) }}</code></td>
                    </tr>

                    <tr v-if="service.name === 'worker'">
                      <td>{{ $t('Queues')}}</td>
                      <td>{{ $t(':') }}</td>
                      <td><code>{{ serviceGroupQueues.map(q => `#${q}`).join(' ') }}</code></td>
                    </tr>
                  </template>

                  <template v-else>
                    <tr>
                      <td>{{ $t('Version') }}</td>
                      <td>{{ $t(':') }}</td>
                      <td><code>{{ service.version }} <span v-if="service.edition">({{ service.edition }})</span></code></td>
                    </tr>
                    <tr>
                      <td>{{ $t('Hostname') }}</td>
                      <td>{{ $t(':') }}</td>
                      <td><code>{{ service.hostname }}</code></td>
                    </tr>
                    <tr>
                      <td>{{ $t('Process ID') }}</td>
                      <td>{{ $t(':') }}</td>
                      <td><code>{{ service.pid }}</code></td>
                    </tr>

                    <tr>
                      <td>{{ $t('Time') }}</td>
                      <td>{{ $t(':') }}</td>
                      <td>
                        <code>{{ T.getDateTimeString(service.ts * 1000) }}</code>
                        <span :class="{ 'text-bad': Math.abs(service.timeDiffMs) > DANGER_TIME_DIFF_MS }">
                          {{ $t('(') }}<TimeDuration :duration="service.timeDiffMs" unit="ms" />{{ $t(')') }}
                        </span>
                      </td>
                    </tr>
                    <tr>
                      <td>{{ $t('Up Time') }}</td>
                      <td>{{ $t(':') }}</td>
                      <td><code>{{ T.duration(service.uptime * 1000) }}</code></td>
                    </tr>

                    <tr v-if="service.queues">
                      <td>{{ $t('Queues')}}</td>
                      <td>{{ $t(':') }}</td>
                      <td><code>{{ service.queues.map(q => `#${q}`).join(' ') }}</code></td>
                    </tr>
                  </template>
                </table>

                <div class="service-active">
                  <el-progress :percentage="serviceGroupCollapsed ? 100 : service.activePercent" :format="serviceActiveFormat" :color="serviceActiveColor"></el-progress>
                </div>
              </div>
            </el-card>
            <el-card v-if="group.length > 0 && serviceGroupCollapsed" class="service-group-count">
              <span class="service-group-count-text">
                &times;
                {{ group.length }}
              </span>
            </el-card>
          </div>
        </template>

        <el-divider content-position="left"><h1>{{ $t('Queues') }}</h1></el-divider>
        <el-card v-for="q, i in queues"
          class="queue-card"
          :class="{ 'queue-highlight': q.workerQueueLength > 0 }"
          shadow="hover"
          :key="i">
          <div class="queue-info">
            <span class="queue-number" :class="{ 'text-bad': (q.workerCount <= 0 || q.processCount <= 0) }">#{{ i }}</span>
            <table>
              <tbody>
                <tr :class="{ 'text-bad': (q.workerCount <= 0) }">
                  <td>{{ $t('Worker') }}</td>
                  <td>{{ $t(':') }}</td>
                  <td>{{ $tc('generalCount', q.workerCount) }}</td>
                </tr>
                <tr :class="{ 'text-bad': (q.processCount <= 0) }">
                  <td>{{ $t('Process') }}</td>
                  <td>{{ $t(':') }}</td>
                  <td>{{ $tc('generalCount', q.processCount) }}</td>
                </tr>
                <tr>
                  <td>{{ $t('Delayed') }}</td>
                  <td>{{ $t(':') }}</td>
                  <td><span class="cover">{{ $tc('taskCount', q.delayQueueLength) }}</span></td>
                </tr>
                <tr :class="{ 'text-good': (q.workerQueueLoad < 50), 'text-watch': (q.workerQueueLoad >= 50 && q.workerQueueLoad < 99), 'text-bad': (q.workerQueueLoad >= 100) }">
                  <td>{{ $t('Worker Queue') }}</td>
                  <td>{{ $t(':') }}</td>
                  <td><span class="cover">{{ $tc('taskCount', q.workerQueueLength) }}</span></td>
                </tr>
                <tr v-if="q.workerQueueLimit" class="text-main">
                  <td>{{ $t('Queue Limit') }}</td>
                  <td>{{ $t(':') }}</td>
                  <td>
                    <el-tooltip effect="dark" :content="$t('When the Work Queue length reaches the limit, Cron Jobs will stop generating new tasks')" placement="bottom">
                      <span class="cover">
                        &le; {{ $tc('taskCount', q.workerQueueLimit) }}
                        <i class="fa fa-fw fa-question-circle"></i>
                      </span>
                    </el-tooltip>
                  </td>
                </tr>
              </tbody>
            </table>
          </div>

          <el-progress type="circle" width="100"
            class="worker-queue-load"
            :percentage="q.workerQueueLoad"
            :format="workerQueueLoadFormat"
            :color="WORKER_QUEUE_LOAD_COLORS"></el-progress>
        </el-card>

        <el-divider content-position="left"><h1>{{ $t('Biz Metrics') }}</h1></el-divider>
        <el-card class="biz-metric-card" shadow="hover" v-for="d in bizMetrics" :key="d.name">
          <span class="biz-metric-title">{{ d.isBuiltin ? $t(d.title) : d.title }}</span>
          <span class="biz-metric-sub-title">{{ d.isBuiltin ? $t(d.subTitle) : d.subTitle }}</span>
          <span class="biz-metric-value">{{ 'number' === typeof d.value ? T.numberComma(d.value) : d.value }}</span>
        </el-card>

        <el-divider content-position="left"><h1>{{ $t('Biz Entities') }}</h1></el-divider>
        <el-card class="biz-entity-card" shadow="hover" v-for="d in bizEntities" :key="d.name">
          <i v-if="C.OVERVIEW_ENTITY_MAP.get(d.name).icon" class="fa fa-fw biz-entity-icon" :class="C.OVERVIEW_ENTITY_MAP.get(d.name).icon"></i>
          <i v-else-if="C.OVERVIEW_ENTITY_MAP.get(d.name).tagText" type="info" class="biz-entity-icon biz-entity-icon-text"><code>{{ C.OVERVIEW_ENTITY_MAP.get(d.name).tagText }}</code></i>

          <span class="biz-entity-name">{{ C.OVERVIEW_ENTITY_MAP.get(d.name).name }}</span>

          <template v-if="T.notNothing(d.countEnabled)">
            <span class="biz-entity-count">
              <el-tooltip effect="dark" :content="$t('Exclude Disabled Items')" placement="left">
                <span class="text-good">
                  {{ T.numberComma(d.countEnabled) }}
                </span>
              </el-tooltip>
            </span>
            <span class="biz-entity-count-sub">
              {{ $t('Total') }}{{ $t(':') }}
              {{ T.numberComma(d.count) }}
            </span>
          </template>

          <template v-else>
            <span class="biz-entity-count">
              {{ T.numberComma(d.count) }}
            </span>
          </template>
        </el-card>
      </el-main>

      <LongTextDialog :showDownload="true" ref="longTextDialog" />
    </el-container>
  </transition>
</template>

<script>
import TimeDuration from '@/components/TimeDuration'
import LongTextDialog from '@/components/LongTextDialog'

export default {
  name: 'Overview',
  components: {
    TimeDuration,
    LongTextDialog,
  },
  watch: {
    $route: {
      immediate: true,
      async handler(to, from) {
        await this.loadData();
      }
    },
  },
  methods: {
    async loadData(sections, options) {
      options = options || {};

      let _query = null;
      if (this.T.notNothing(sections)) {
        _query = { sections: sections.join(',') };
      }
      let apiRes = await this.T.callAPI_get('/api/v1/func/overview', {
        query   : _query,
        feedback: { muteError: options.mute },
      });
      if (!apiRes || !apiRes.ok) return;

      this.browserServerTimeDiff = new Date(apiRes.clientTime) - new Date(apiRes.reqTime);

      for (let section in apiRes.data) {
        this[section] = apiRes.data[section];

        switch (section) {
          case 'services':
            // Compute the progress bar
            let heartbeatInterval = this.$store.getters.SYSTEM_INFO('_HEARTBEAT_INTERVAL');
            this.services.forEach(s => {
              if (s.ttl >= heartbeatInterval) {
                s.activePercent = 100;
                s.activeStatus  = 'success';
              } else {
                s.activePercent = s.ttl * 100 / heartbeatInterval;
                s.activeStatus  = 'warning';
              }
            });
            break;
        }
      }

      this.$store.commit('updateLoadStatus', true);
    },
    serviceActiveFormat(percentage) {
      return percentage >= 100 ? this.$t('Online') : this.$t('Going Offline');
    },
    serviceActiveColor(percentage) {
      return percentage >= 100 ? 'rgb(0,128,0)' : 'orange';
    },
    overviewCountFontSize(count, k) {
      let numberLength = ('' + count).length;
      let fontSize = parseInt(80 - numberLength * 10 * (k || 1));
      return Math.max(50, fontSize);
    },
    workerQueueLoadFormat(percentage) {
      return `${this.$t('Load')}${this.$t(':')}${this.T.numberLimit(percentage, 999)}`;
    },
  },
  computed: {
    DANGER_TIME_DIFF_MS() {
      return 3000;
    },
    WORKER_QUEUE_LOAD_COLORS() {
      return [
        { color: '#00aa00', percentage: 50 },
        { color: '#ff6600', percentage: 99 },
        { color: '#ff0000', percentage: 100 }
      ];
    },
    serviceGroup_servers() {
      return this.services.filter(s => s.name === 'server');
    },
    serviceGroup_workers() {
      return this.services.filter(s => s.name === 'worker');
    },
    serviceGroup_beat() {
      return this.services.filter(s => s.name === 'beat');
    },

    serviceGroupTime_avg() {
      return {
        server: parseInt(this.serviceGroup_servers.reduce((acc, x) => acc + x.ts, 0) / this.serviceGroup_servers.length),
        worker: parseInt(this.serviceGroup_workers.reduce((acc, x) => acc + x.ts, 0) / this.serviceGroup_workers.length),
        beat  : parseInt(this.serviceGroup_beat.reduce((acc, x)    => acc + x.ts, 0) / this.serviceGroup_beat.length),
      }
    },
    serviceGroupTime_min() {
      return {
        server: Math.min.apply(null, this.serviceGroup_servers.map(x => x.ts)),
        worker: Math.min.apply(null, this.serviceGroup_workers.map(x => x.ts)),
        beat  : Math.min.apply(null, this.serviceGroup_beat.map(x => x.ts)),
      }
    },
    serviceGroupTime_max() {
      return {
        server: Math.max.apply(null, this.serviceGroup_servers.map(x => x.ts)),
        worker: Math.max.apply(null, this.serviceGroup_workers.map(x => x.ts)),
        beat  : Math.max.apply(null, this.serviceGroup_beat.map(x => x.ts)),
      }
    },
    serviceGroupTimeDiffMs_avg() {
      return {
        server: parseInt(this.serviceGroup_servers.reduce((acc, x) => acc + x.timeDiffMs, 0) / this.serviceGroup_servers.length),
        worker: parseInt(this.serviceGroup_workers.reduce((acc, x) => acc + x.timeDiffMs, 0) / this.serviceGroup_workers.length),
        beat  : parseInt(this.serviceGroup_beat.reduce((acc, x)    => acc + x.timeDiffMs, 0) / this.serviceGroup_beat.length),
      }
    },
    serviceGroupTimeDiffMs_min() {
      return {
        server: Math.min.apply(null, this.serviceGroup_servers.map(x => x.timeDiffMs)),
        worker: Math.min.apply(null, this.serviceGroup_workers.map(x => x.timeDiffMs)),
        beat  : Math.min.apply(null, this.serviceGroup_beat.map(x => x.timeDiffMs)),
      }
    },
    serviceGroupTimeDiffMs_max() {
      return {
        server: Math.max.apply(null, this.serviceGroup_servers.map(x => x.timeDiffMs)),
        worker: Math.max.apply(null, this.serviceGroup_workers.map(x => x.timeDiffMs)),
        beat  : Math.max.apply(null, this.serviceGroup_beat.map(x => x.timeDiffMs)),
      }
    },
    serviceGroupUptime_avg() {
      return {
        server: parseInt(this.serviceGroup_servers.reduce((acc, x) => acc + x.uptime, 0) / this.serviceGroup_servers.length),
        worker: parseInt(this.serviceGroup_workers.reduce((acc, x) => acc + x.uptime, 0) / this.serviceGroup_workers.length),
        beat  : parseInt(this.serviceGroup_beat.reduce((acc, x)    => acc + x.uptime, 0) / this.serviceGroup_beat.length),
      }
    },
    serviceGroupUptime_min() {
      return {
        server: Math.min.apply(null, this.serviceGroup_servers.map(x => x.uptime)),
        worker: Math.min.apply(null, this.serviceGroup_workers.map(x => x.uptime)),
        beat  : Math.min.apply(null, this.serviceGroup_beat.map(x => x.uptime)),
      }
    },
    serviceGroupUptime_max() {
      return {
        server: Math.max.apply(null, this.serviceGroup_servers.map(x => x.uptime)),
        worker: Math.max.apply(null, this.serviceGroup_workers.map(x => x.uptime)),
        beat  : Math.max.apply(null, this.serviceGroup_beat.map(x => x.uptime)),
      }
    },
    serviceGroupVersionEditions() {
      let _reduceFunc = (acc, x) => {
        let versionEdition = `${x.version}`;
        if (x.edition) versionEdition += ` (${x.edition})`;

        acc.push(versionEdition);
        acc = this.T.noDuplication(acc);
        acc.sort();
        return acc;
      }
      return {
        server: this.serviceGroup_servers.reduce(_reduceFunc, []),
        worker: this.serviceGroup_workers.reduce(_reduceFunc, []),
        beat  : this.serviceGroup_beat.reduce(_reduceFunc, []),
      }
    },

    serviceGroupQueues() {
      let _reduceFunc = (acc, x) => {
        acc = acc.concat(x.queues || []);
        acc = this.T.noDuplication(acc);
        this.T.sort(acc);
        return acc;
      }
      return this.serviceGroup_workers.reduce(_reduceFunc, []);
    },
  },
  props: {
  },
  data() {
    return {
      browserServerTimeDiff: 0,
      serviceGroupCollapsed: true,

      services   : [],
      queues     : [],
      bizMetrics : [],
      bizEntities: [],

      autoRefreshTimer: null,
    }
  },
  mounted() {
    this.autoRefreshTimer = setInterval(() => {
      this.loadData([ 'services', 'queues' ], { mute: true });
    }, 5 * 1000);
  },
  beforeDestroy() {
    if (this.autoRefreshTimer) clearInterval(this.autoRefreshTimer);
  },
}
</script>

<style scoped>
.browser-server-time-diff {
  display: block;
  position: relative;
  padding: 0 0 10px 10px;
  font-size: 12px;
  z-index: 1;
}

.queue-card {
  width: 300px;
  height: 180px;
  display: inline-block;
  margin: 10px 10px;
  position: relative;
}
.queue-highlight {
  color: var(--primary);
  border-color: var(--primary);
}
.queue-info {
  font-size: 12px;
  text-align: left;
}
.queue-info .queue-number {
  font-size: 40px;
  letter-spacing: 5px;
}
.queue-info table {
  z-index: 1;
  position: relative;
}
.worker-queue-load {
  top: 25px;
  right: 25px;
  position: absolute;
}

.service-card {
  width: 325px;
  min-height: 205px;
  display: inline-block;
  margin: 10px 10px;
  position: relative;
}
.service-card-collapsed {
  min-height: 245px;
}
.service-info {
  font-size: 12px;
  text-align: left;
}
.service-info .service-name {
  text-transform: capitalize;
  font-size: 30px;
}
.service-active {
  position: absolute;
  top: 15px;
  right: 15px;
}

.service-info tr > td,
.queue-info tr > td {
  vertical-align: top;
}
.service-info tr > td:first-child,
.queue-info tr > td:first-child {
  white-space: nowrap;
}

.service-group-expand-button {
  margin-left: 10px;
}
.service-group {
  vertical-align: top;
}
.service-group-collapsed {
  display: inline-block;
}
.service-group-rest {
  display: none;
}
.service-group-count {
  width: 100px;
  height: 160px;
  display: inline-block;
  margin: 10px 30px 10px -10px;
  position: relative;
  border: none !important;
  box-shadow: none !important;
}
.service-group-count-text {
  font-size: 32px;
  position: absolute;
  bottom: 10px;
  left: 10px;
}

.biz-metric-card {
  width: 300px;
  height: 140px;
  display: inline-block;
  margin: 10px 10px;
  position: relative;
}
.biz-metric-title {
  font-size: 22px;
  display: block;
  z-index: 1;
  position: relative;
}
.biz-metric-sub-title {
  font-size: 16px;
  display: block;
  z-index: 1;
  position: relative;
  opacity: .7;
}
.biz-metric-value {
  font-size: 42px;
  display: block;
  z-index: 1;
  position: relative;
  margin-top: 5px;
  text-align: right;
}

.biz-entity-card {
  width: 300px;
  height: 165px;
  display: inline-block;
  margin: 10px 10px;
  position: relative;
}
.biz-entity-icon {
  position: absolute;
  font-size: 150px;
  left: 5px;
  top: 39px;
  color: var(--text-water-marker);
  line-height: 200px;
  z-index: 0;
}
.biz-entity-icon-text {
  font-size: 120px;
}
.biz-entity-name {
  font-size: 32px;
  display: block;
  z-index: 1;
  position: relative;
}
.biz-entity-count {
  line-height: 1.5;
  font-family: sans-serif;
  display: block;
  padding-left: 20px;
  z-index: 1;
  position: relative;
  font-size: 42px;
  text-align: right;
}
.biz-entity-count-sub {
  font-size: 18px;
  position: absolute;
  right: 20px;
}
</style>

<style>
.el-card .cover {
  background-color: var(--base);
}

.service-active .el-progress-bar {
  width: 100px;
}
.service-active .el-progress__text {
  font-size: 12px !important;
}

.queue-card .el-card__body,
.service-card .el-card__body,
.service-group-count .el-card__body {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-bottom: 5px;
}
</style>
